﻿using System;
using System.Collections.Generic;

using BizElements.BusinessLayer;

namespace BizElements.Web
{
    /// <summary>
    /// Generic parametrized command.
    /// </summary>
    /// <remarks><para>Commands provide execution logic and security checks. A client or another component is responsible 
    /// to display them.</para>
    /// <para>Commands must be cloneable (prototype pattern). Cloned command must have exactly the same behavior 
    /// but its state is reset. Ie. <see cref="Parameters"/>, <see cref="ContextVariables"/> collections and 
    /// other/custom state members must be empty/nulled.</para></remarks>
    public interface ICommand : ICloneable
    {
        #region Cloneable properties - command definition.

        /// <summary>
        /// Gets or sets command key.
        /// </summary>
        /// <remarks>Command key should be unique in the collection and language neutral. Implementations 
        /// should return <see cref="Text"/> value if Key property wasn't explicitly set.</remarks>
        string Key { get; set; }

        /// <summary>
        /// Gets command text displayed in UI.
        /// </summary>
        string Text { get; }

        /// <summary>
        /// Gets an array of parameter names which must be provided before invoking <b>Execute</b> method.
        /// </summary>
        string[] RequiredParameters { get; }

        /// <summary>
        /// Gets the minimum number of records (<b>Parameters.Count</b>) which must be provided before the command is executed.
        /// </summary>
        /// <remarks>Should be set to <b>zero</b> if <see cref="RequiredParameters"/> array is empty. Typically
        /// set to <b>one</b> if RequiredParameters is not empty.</remarks>
        int MinParameterSetSize { get; }

        /// <summary>
        /// Gets the maximum number of records (<b>Parameters.Count</b>) supported by the command.
        /// </summary>
        /// <remarks>Should be set to zero if <see cref="RequiredParameters"/> array is empty. Set to <b>one</b> 
        /// if RequiredParameters is not empty and the command cannot operate on multiple records.</remarks>
        int MaxParameterSetSize { get; }

        /// <summary>
        /// Gets an array of context variable names which must be provided to command for it to operate.
        /// </summary>
        string[] RequiredContextVariables { get; }

        /// <summary>
        /// Gets or sets optional formatter which formats command parameters when they need to be reprepresented as text.
        /// </summary>
        IMessageFormatter ParameterFormatter { get; set; }

        #endregion

        #region Uncloneable properties - command parameters.

        /// <summary>
        /// Gets parameter-set provided by client.
        /// </summary>
        /// <remarks><para><b>Dictionary</b> is filled with parameter names and values. All required
        /// parameters (<see cref="RequiredParameters"/>) must be set foreach dictionary entry.</para>
        /// <para>Some commands allow variable number of records on which they operate. 
        /// Some don't require any parameters.</para>
        /// <para>This property should not be cloned.</para></remarks>
        List<Dictionary<string, object>> Parameters { get; }

        /// <summary>
        /// Gets the table which contains key-value pairs where key represents the variable name.
        /// </summary>
        /// <remarks><para>Context variables affect how the command is conducted. Context variables may be provided
        /// to a command by explicitly setting it in client or mediator, but some commands may automatically 
        /// read variable from the execution context (eg. query string, session state...).</para>
        /// <para>This property should not be cloned.</para></remarks>
        Dictionary<string, string> ContextVariables { get; }

        #endregion

        #region Methods.

        /// <summary>
        /// Executes the command with provided parameters.
        /// </summary>
        /// <param name="caller">Object/client which is calling the method.</param>
        /// <remarks>The behavior of this overload should be the same as if a <b>null</b> reference instead of
        /// an <see cref="IActor"/> instance is provided to <see cref="Execute(object, IActor)"/> overload.</remarks>
        void Execute(object caller);

        /// <summary>
        /// Executes the command with provided parameters.
        /// </summary>
        /// <param name="caller">Object/client which is calling the method.</param>
        /// <param name="actor">Command initiator. This may be an application user, software module, 3rd party 
        /// application etc. which is initiating the command.</param>
        /// <remarks>This overload should be used when the command should perform additional security
        /// checks (vertical/horizontal) or when the command execution must be logged. Preffer using
        /// this overload whenever the <see cref="IActor"/> object is available.</remarks>
        void Execute(object caller, IActor actor);

        /// <summary>
        /// Tries to prepare and execute the command.
        /// </summary>
        /// <param name="caller">Object/client which is calling the method.</param>
        /// <param name="actor">Command initiator. This may be an application user, software module, 3rd party 
        /// application etc. which is initiating the command.</param>
        /// <returns><b>true</b> if all required parameters where provided and the command was executed; otherwise <b>false</b>.</returns>
        bool TryExecute(object caller, IActor actor);

        /// <summary>
        /// Checks whether the command is available to the given actor.
        /// </summary>
        /// <param name="actor">Actor whose permissions are to be checked.</param>
        /// <returns>Collection of permissions (broken rules) that the actor has failed.</returns>
        /// <remarks>The method may be called before the command parameters have been provided
        /// to enforce vertical security.</remarks>
        BusinessRuleCollection IsAvailableToActor(IActor actor);

        /// <summary>
        /// Checks whether the actor has permissions to execute the command with the provided <b>Parameters</b>.
        /// </summary>
        /// <param name="actor">Actor whose permissions are to be checked.</param>
        /// <returns>Collection of permissions (broken rules) that the actor has failed.</returns>
        /// <remarks>The method should be called after all parameters have been set and before the 
        /// <b>Execute</b> method is invoked. Typically used to enforce horizontal security.</remarks>
        BusinessRuleCollection GrantExecute(IActor actor);

        /// <summary>
        /// Assigns a method delegate which provides parameters for the ICommand object.
        /// </summary>
        /// <param name="parametersSetter">A method which sets command parameters. Nullable.</param>
        void SetParameterProviderMethod(Delegates.Procedure<ICommand> parametersSetter);

        /// <summary>
        /// Determines whether the command has been prepared. If not, it tries to prepare the parameters 
        /// and context variables required for execution.
        /// </summary>
        /// <returns><b>true</b> if the command is prepared and <b>Execute</b> and <b>GrantExecute</b> 
        /// may be invoked; otherwise <b>false</b>.</returns>
        bool PrepareForExecution();

        #endregion
    }
}